package com.asiainfo.test.config;

import com.zaxxer.hikari.HikariDataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertyNameAliases;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

import static com.asiainfo.test.config.DataSourceProperties.isPrimary;

/**
 * 多数据源配置
 */

@Configuration
@Import(DataSourceAutoConfig.Registrar.class)
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceAutoConfig {

    public static final Logger log = LoggerFactory.getLogger(DataSourceAutoConfig.class);
    private static Map<String, DataSource> dataSourceMap = new HashMap<>();

    static class Registrar implements ImportBeanDefinitionRegistrar {

        /**
         * 注册数据源
         */
        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                                            BeanDefinitionRegistry registry) {
            try {
                DataSourceProperties.get().forEach((name, value) -> {
                    boolean primary = isPrimary(value);
                    String dataSourceBeanName = getDataSourceBeanName(name);
                    registry.registerBeanDefinition(
                            dataSourceBeanName,
                            BeanDefinitionBuilder
                                    .rootBeanDefinition(DataSourceBeanFactory.class, "createInstance")
                                    .setPrimary(primary)
                                    .setDestroyMethodName("close")
                                    .addConstructorArgValue(name)
                                    .getBeanDefinition()
                    );
                    if (primary)
                        log.info("===> Register {} primary DataSource", name);
                    else
                        log.info("===> Register {} DataSource", name);
                });
            } catch (Exception e) {
                log.error("Register DataSource error", e);
                throw new RuntimeException(e);
            }
        }

    }


    static class DataSourceBeanFactory {
        public static DataSource createInstance(String name) throws Exception {
            DataSourceProperties properties = DataSourceProperties.get();
            Map<String, Object> value = properties.get(name);
            String typeName = value.getOrDefault("type", HikariDataSource.class.getName()).toString();
            Class type = Class.forName(typeName);
            DataSource ds = DataSourceBuilder.create().type(type).build();
            bindPropertyValue(ds, value);
            addDataSource(name, ds);
            return ds;
        }

        private static void bindPropertyValue(Object target, Map<String, Object> value) {
            ConfigurationPropertySource source = new MapConfigurationPropertySource(value);
            ConfigurationPropertyNameAliases aliases = new ConfigurationPropertyNameAliases();
            aliases.addAliases("url", "jdbc-url");
            aliases.addAliases("username", "user");
            Binder binder = new Binder(source.withAliases(aliases));
            binder.bind(ConfigurationPropertyName.EMPTY, Bindable.ofInstance(target));
        }

        private static void addDataSource(String name, DataSource ds) {
            String dataSourceBeanName = getDataSourceBeanName(name);
            dataSourceMap.put(dataSourceBeanName, ds);
        }
    }

    public static String getDataSourceBeanName(String name) {
        return name + "DataSource";
    }

    public static DataSource getDataSource(String dataSourceBeanName) {
        return dataSourceMap.get(dataSourceBeanName);
    }

}
